/*
 * Copyright (c) 2003
 *	The University of Guelph.  All rights reserved.
 *
 * This software was written by Rick Macklem and was derived in part from
 * software in various BSD releases covered by the
 * subsequent copyright notices.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * It is also requested that vendors of products using all or part of this
 * software acknowledge that their products are based upon open source
 * written by Rick Macklem at the University of Guelph.
 *
 * THIS SOFTWARE IS PROVIDED BY THE UNIVERSITY OF GUELPH ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE UNIVERSITY OF GUELPH BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */
/*	$OpenBSD: nfs_socket.c,v 1.15 1999/03/02 20:59:55 millert Exp $	*/
/*	$NetBSD: nfs_socket.c,v 1.27 1996/04/15 20:20:00 thorpej Exp $	*/

/*
 * Copyright (c) 1989, 1991, 1993, 1995
 *	The Regents of the University of California.  All rights reserved.
 *
 * This code is derived from software contributed to Berkeley by
 * Rick Macklem at The University of Guelph.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *	This product includes software developed by the University of
 *	California, Berkeley and its contributors.
 * 4. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 *	@(#)nfs_socket.c	8.5 (Berkeley) 3/30/95
 */

/*
 * Socket operations for use by nfs
 */
#define	TRUE	1
#define	FALSE	0

#ifndef APPLEKEXT
#include <newnfs/nfs/nfsport.h>
/*
 * External data, mostly RPC constants in XDR form
 */
extern u_int32_t newrpc_reply, newrpc_msgdenied, newrpc_mismatch, newrpc_vers,
    newrpc_msgaccepted, newrpc_call, newrpc_autherr, newnfs_xidwrap;
struct nfsstats newnfsstats;
extern int nfscl_ticks;
extern int nfsrv_debug;
extern struct nfsreqhead nfsd_reqq;
int nfstest_nobackoff = 0;
int nfstest_dropreplies = 0;
int nfstest_v4udp = 0;
NFSREQSPINLOCK;
NFSSTATESPINLOCK;
NFSSOCKMUTEX;
int newnfsrtton = 0;
struct nfsrtt newnfsrtt;

/*
 * This static array of structures indicates, for V4:
 * retfh - which of 3 types of calling args are used
 *	0 - doesn't change cfh or use a sfh
 *	1 - replaces cfh with a new one (unless it returns an error status)
 *	2 - uses cfh and sfh
 * needscfh - if the op wants a cfh and premtime
 *	0 - doesn't use a cfh
 *	1 - uses a cfh, but doesn't want pre-op attributes
 *	2 - uses a cfh and wants pre-op attributes
 * savereply - indicates a non-idempotent Op
 *	0 - not non-idempotent
 *	1 - non-idempotent
 * Ops that are ordered via seqid# are handled separately from these
 * non-idempotent Ops.
 */
struct nfsv4_opflag nfsv4_opflag[NFSV4OP_NOPS] = {
	{ 0, 0, 0, 0 },		/* undef */
	{ 0, 0, 0, 0 },		/* undef */
	{ 0, 0, 0, 0 },		/* undef */
	{ 0, 1, 0, 0 },		/* Access */
	{ 0, 1, 0, 0 },		/* Close */
	{ 0, 2, 0, 1 },		/* Commit */
	{ 1, 2, 1, 1 },		/* Create */
	{ 0, 0, 0, 0 },		/* Delegpurge */
	{ 0, 1, 0, 0 },		/* Delegreturn */
	{ 0, 1, 0, 0 },		/* Getattr */
	{ 0, 1, 0, 0 },		/* GetFH */
	{ 2, 1, 1, 1 },		/* Link */
	{ 0, 1, 0, 0 },		/* Lock */
	{ 0, 1, 0, 0 },		/* LockT */
	{ 0, 1, 0, 0 },		/* LockU */
	{ 1, 1, 0, 0 },		/* Lookup */
	{ 1, 1, 0, 0 },		/* Lookupp */
	{ 0, 1, 0, 0 },		/* NVerify */
	{ 1, 1, 0, 1 },		/* Open */
	{ 1, 1, 0, 0 },		/* OpenAttr */
	{ 0, 1, 0, 0 },		/* OpenConfirm */
	{ 0, 1, 0, 0 },		/* OpenDowngrade */
	{ 1, 0, 0, 0 },		/* PutFH */
	{ 1, 0, 0, 0 },		/* PutPubFH */
	{ 1, 0, 0, 0 },		/* PutRootFH */
	{ 0, 1, 0, 0 },		/* Read */
	{ 0, 1, 0, 0 },		/* Readdir */
	{ 0, 1, 0, 0 },		/* ReadLink */
	{ 0, 2, 1, 1 },		/* Remove */
	{ 2, 1, 1, 1 },		/* Rename */
	{ 0, 0, 0, 0 },		/* Renew */
	{ 0, 0, 0, 0 },		/* RestoreFH */
	{ 0, 1, 0, 0 },		/* SaveFH */
	{ 0, 1, 0, 0 },		/* SecInfo */
	{ 0, 2, 1, 1 },		/* Setattr */
	{ 0, 0, 0, 0 },		/* SetClientID */
	{ 0, 0, 0, 0 },		/* SetClientIDConfirm */
	{ 0, 1, 0, 0 },		/* Verify */
	{ 0, 2, 1, 1 },		/* Write */
	{ 0, 0, 0, 0 },		/* ReleaseLockOwner */
};

/*
 * Estimate rto for an nfs rpc sent via. an unreliable datagram.
 * Use the mean and mean deviation of rtt for the appropriate type of rpc
 * for the frequent rpcs and a default for the others.
 * The justification for doing "other" this way is that these rpcs
 * happen so infrequently that timer est. would probably be stale.
 * Also, since many of these rpcs are
 * non-idempotent, a conservative timeout is desired.
 * getattr, lookup - A+2D
 * read, write     - A+4D
 * other           - nm_timeo
 */
#define	NFS_RTO(n, t) \
	((t) == 0 ? (n)->nm_timeo : \
	 ((t) < 3 ? \
	  (((((n)->nm_srtt[t-1] + 3) >> 2) + (n)->nm_sdrtt[t-1] + 1) >> 1) : \
	  ((((n)->nm_srtt[t-1] + 7) >> 3) + (n)->nm_sdrtt[t-1] + 1)))
#define	NFS_SRTT(r)	(r)->r_nmp->nm_srtt[proct[(r)->r_procnum] - 1]
#define	NFS_SDRTT(r)	(r)->r_nmp->nm_sdrtt[proct[(r)->r_procnum] - 1]
#endif	/* !APPLEKEXT */

/*
 * This static array indicates whether or not the RPC generates a large
 * reply. This is used by nfs_reply() to decide whether or not an mbuf
 * cluster should be allocated. (If a cluster is required by an RPC
 * marked 0 in this array, the code will still work, just not quite as
 * efficiently.)
 */
static int nfs_bigreply[NFS_NPROCS] = { 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0 };

/*
 * Mapping of old NFS Version 2 RPC numbers to generic numbers.
 */
static int nfsv3_procid[NFS_V3NPROCS] = {
	NFSPROC_NULL,
	NFSPROC_GETATTR,
	NFSPROC_SETATTR,
	NFSPROC_NOOP,
	NFSPROC_LOOKUP,
	NFSPROC_READLINK,
	NFSPROC_READ,
	NFSPROC_NOOP,
	NFSPROC_WRITE,
	NFSPROC_CREATE,
	NFSPROC_REMOVE,
	NFSPROC_RENAME,
	NFSPROC_LINK,
	NFSPROC_SYMLINK,
	NFSPROC_MKDIR,
	NFSPROC_RMDIR,
	NFSPROC_READDIR,
	NFSPROC_FSSTAT,
	NFSPROC_NOOP,
	NFSPROC_NOOP,
	NFSPROC_NOOP,
	NFSPROC_NOOP,
};

#ifndef APPLE
static int nfs_reconnect (struct nfsreq *, struct ucred *);
static int nfs_receive (struct nfsreq *, NFSSOCKADDR_T *, mbuf_t *,
    struct ucred *, NFSPROC_T *);
static int nfs_reply (struct nfsreq *, struct nfsrv_descript *,
    struct ucred *, NFSPROC_T *);
static int nfs_rcvlock (struct nfsreq *);
static void nfs_rcvunlock (int *);

/*
 * Defines which timer to use for the procnum.
 * 0 - default
 * 1 - getattr
 * 2 - lookup
 * 3 - read
 * 4 - write
 */
static int proct[NFS_NPROCS] = {
	0, 1, 0, 2, 1, 3, 3, 4, 0, 0, 0, 0, 0, 0, 0, 0, 3, 3, 0, 0, 0, 0,
	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
};

/*
 * There is a congestion window for outstanding rpcs maintained per mount
 * point. The cwnd size is adjusted in roughly the way that:
 * Van Jacobson, Congestion avoidance and Control, In "Proceedings of
 * SIGCOMM '88". ACM, August 1988.
 * describes for TCP. The cwnd size is chopped in half on a retransmit timeout
 * and incremented by 1/cwnd when each rpc reply is received and a full cwnd
 * of rpcs is in progress.
 * (The sent count and cwnd are scaled for integer arith.)
 * Variants of "slow start" were tried and were found to be too much of a
 * performance hit (ave. rtt 3 times larger),
 * I suspect due to the large rtt that nfs rpcs have.
 */
static int nfs_backoff[8] = { 2, 4, 8, 16, 32, 64, 128, 256, };

/*
 * Reconnect routine:
 * Called when a connection is broken on a reliable protocol.
 * - clean up the old socket
 * - newnfs_connect() again
 * - set R_MUSTRESEND for all outstanding requests on mount point
 * If this fails the mount point is DEAD!
 * nb: Must be called with the newnfs_sndlock() set on the mount point.
 */
int
nfs_reconnect(struct nfsreq *rep, struct ucred *cred)
{
	struct nfsreq *rp;
	struct nfssockreq *nrp;
	int error;

	nrp = rep->r_nrp;
	newnfs_disconnect(nrp);
	while ((error = newnfs_connect(rep->r_nmp,nrp,rep,cred,rep->r_procp))
		!= 0) {
DEBUG2PRINTF("reconn newnfs_conn err=%d\n",error);
		if (error == EINTR || error == ERESTART)
			return (EINTR);
		(void) tsleep((caddr_t)&lbolt, PSOCK, "nfscon", 0);
	}

	/*
	 * Loop through outstanding request list and fix up all requests
	 * on old socket.
	 */
	TAILQ_FOREACH(rp, &nfsd_reqq, r_chain) {
		if (rp->r_nrp == nrp)
			rp->r_flags |= R_MUSTRESEND;
	}
	return (0);
}

/*
 * NFS disconnect. Clean up and unlink.
 */
void
newnfs_disconnect(struct nfssockreq *nrp)
{
	struct socket *so;

DEBUG2PRINTF("in newnfs_disconn\n");
	NFSTONETWORK;
	if (nrp->nr_so) {
		so = nrp->nr_so;
		nrp->nr_so = (struct socket *)0;
		soshutdown(so, SHUT_RDWR);
		soclose(so);
	}
	NFSTOKERNEL;
}

/*
 * This is the nfs send routine. For connection based socket types, it
 * must be called with an newnfs_sndlock() on the socket.
 * "rep == NULL" indicates that it has been called from a server.
 * For the client side:
 * - return EINTR if the RPC is terminated, 0 otherwise
 * - set R_MUSTRESEND if the send fails for any reason
 * - do any cleanup required by recoverable socket errors
 * For the server side:
 * - return EINTR or ERESTART if interrupted by a signal
 * - return EPIPE if a connection is lost for connection based sockets (TCP...)
 * - do any cleanup required by recoverable socket errors
 */
int
newnfs_send(struct socket *so, NFSSOCKADDR_T nam, mbuf_t top,
    struct nfsreq *rep, NFSPROC_T *p)
{
	NFSSOCKADDR_T sendnam;
	int error, soflags;

	if (rep) {
		if (rep->r_flags & R_SOFTTERM) {
			mbuf_freem(top);
			return (EINTR);
		}
		if ((so = rep->r_nrp->nr_so) == NULL) {
			rep->r_flags |= R_MUSTRESEND;
			mbuf_freem(top);
			return (0);
		}
		rep->r_flags &= ~R_MUSTRESEND;
		soflags = rep->r_nrp->nr_soflags;
	} else
		soflags = so->so_proto->pr_flags;
DEBUG1PRINTF("newnfs_send soflags=0x%x\n",soflags);
	if ((soflags & PR_CONNREQUIRED) || (so->so_state & SS_ISCONNECTED))
		sendnam = NULL;
	else
		sendnam = nam;

	NFSTONETWORK;
	NFSSOCKSOSEND(so, sendnam, NULL, top, NULL, 0, p, error);
	NFSTOKERNEL;
DEBUG1PRINTF("aft sosend err=%d\n",error);
	if (error == ENOBUFS && so->so_type == SOCK_DGRAM)
		error = 0;
	if (error) {
		if (rep != NULL) {
		    /*
		     * Deal with errors for the client side.
		     */
		    if (rep->r_flags & R_SOFTTERM)
			error = EINTR;
		    else
			rep->r_flags |= R_MUSTRESEND;
		}
		if (rep != NULL && rep->r_nmp != NULL)
			log(LOG_INFO, "nfs send error %d for server %s\n",error,
			    rep->r_nmp->nm_mountp->mnt_stat.f_mntfromname);
		else
			log(LOG_INFO, "nfsd send error %d\n", error);

		/*
		 * Handle any recoverable (soft) socket errors here.
		 */
		if (error != EINTR && error != ERESTART &&
			error != EWOULDBLOCK && error != EPIPE)
			error = 0;
	}
	return (error);
}

/*
 * Receive a Sun RPC Request/Reply. For SOCK_DGRAM, the work is all
 * done by NFSSOCKSORCV(), but for SOCK_STREAM we must deal with the Record
 * Mark and consolidate the data into a new mbuf list.
 * nb: Sometimes TCP passes the data up to NFSSOCKSORCV() in long lists of
 *     small mbufs.
 * For SOCK_STREAM we must be very careful to read an entire record once
 * we have read any of it, even if the system call has been interrupted.
 */
static int
nfs_receive(struct nfsreq *rep, NFSSOCKADDR_T *aname, mbuf_t *mp,
    struct ucred *cred, NFSPROC_T *p)
{
	struct socket *so;
	struct uio auio;
	struct iovec aio;
	mbuf_t m;
	mbuf_t control;
	u_int32_t len;
	NFSSOCKADDR_T *getnam;
	int error, sotype, rcvflg;
	struct timespec mytime;
	int32_t rcvtime;

	/*
	 * Set up arguments for NFSSOCKSORCV()
	 */
	*mp = NULL;
	*aname = NULL;
	sotype = rep->r_nrp->nr_sotype;

	/*
	 * For reliable protocols, lock against other senders/receivers
	 * in case a reconnect is necessary.
	 * For SOCK_STREAM, first get the Record Mark to find out how much
	 * more there is to get.
	 * We must lock the socket against other receivers
	 * until we have an entire rpc request/reply.
	 */
	if (sotype != SOCK_DGRAM) {
		error = newnfs_sndlock(&rep->r_nrp->nr_lock, rep);
DEBUG2PRINTF("nfs_rcv aft sndlck err=%d\n",error);
		if (error)
			return (error);
tryagain:
		/*
		 * Check for fatal errors and resending request.
		 */
		/*
		 * Ugh: If a reconnect attempt just happened, nr_so
		 * would have changed. NULL indicates a failed
		 * attempt that has essentially shut down this
		 * mount point.
		 */
		if (rep->r_mrep || (rep->r_flags & R_SOFTTERM)) {
			newnfs_sndunlock(&rep->r_nrp->nr_lock);
DEBUG2PRINTF("nfs_rcv aft softterm unlock\n");
			return (EINTR);
		}
		so = rep->r_nrp->nr_so;
		if (!so) {
DEBUG2PRINTF("nfs_rcv at reconn\n");
			error = nfs_reconnect(rep, cred); 
DEBUG2PRINTF("nfs_rcv recon err=%d\n",error);
			if (error) {
				newnfs_sndunlock(&rep->r_nrp->nr_lock);
				return (error);
			}
			goto tryagain;
		}
		while (rep->r_flags & R_MUSTRESEND) {
DEBUG2PRINTF("nfs_rcv mustresend\n");
			if (rep->r_flags & R_GSS) {
			    /*
			     * For privacy, we must decrypt the data,
			     * so that it can be re-encrypted for the
			     * retry.
			     */
			    if ((rep->r_flags & R_PRIVACY) &&
				rep->r_procnum != NFSPROC_NULL)
				(void)nfsgss_des(rep->r_startm, rep->r_startpos,
				    rep->r_mrestlen + 3 * NFSX_UNSIGNED,
				    rep->r_key, 0);
			    mbuf_setnext(rep->r_mheadend, NULL);
			    mbuf_freem(rep->r_mreq);
			    if (mbuf_next(rep->r_mrestend)) {
				mbuf_freem(mbuf_next(rep->r_mrestend));
				mbuf_setnext(rep->r_mrestend, NULL);
			    }
			    newnfs_rpchead(NULL, rep);
			    if (rep->r_flags & R_AUTHERR) {
				newnfs_sndunlock(&rep->r_nrp->nr_lock);
				return (EAUTH);
			    }
			    if (rep->r_flags & R_SOFTTERM) {
				newnfs_sndunlock(&rep->r_nrp->nr_lock);
				return (EINTR);
			    }
			}
			m = m_copym(rep->r_mreq, 0, M_COPYALL, M_WAIT);
DEBUG2PRINTF("nfs_rcv aft mcopym\n");
			if (rep->r_nmp != NULL)
				NFSINCRGLOBAL(newnfsstats.rpcretries);
			error = newnfs_send(so, rep->r_nrp->nr_nam, m, rep, p);
DEBUG2PRINTF("nfs_rcv resent err=%d\n",error);
			if (error) {
				if (error == EINTR || error == ERESTART ||
				    (error = nfs_reconnect(rep, cred)) != 0) {
					newnfs_sndunlock(&rep->r_nrp->nr_lock);
DEBUG2PRINTF("reconn2 err=%d\n",error);
					return (error);
				}
				goto tryagain;
			}
		}
		newnfs_sndunlock(&rep->r_nrp->nr_lock);
		if (sotype == SOCK_STREAM) {
			aio.iov_base = (caddr_t) &len;
			aio.iov_len = sizeof(u_int32_t);
			auio.uio_iov = &aio;
			auio.uio_iovcnt = 1;
			auio.uio_segflg = UIO_SYSSPACE;
			auio.uio_rw = UIO_READ;
			auio.uio_offset = 0;
			auio.uio_resid = sizeof(u_int32_t);
			NFSUIOPROC(&auio, p);
			NFSGETNANOTIME(&mytime);
			rcvtime = (u_int32_t)mytime.tv_sec;
			NFSTONETWORK;
			do {
			   rcvflg = MSG_WAITALL;
			   NFSSOCKSORCV(so, NULL, &auio, NULL, NULL,
				&rcvflg, error);
			   if (error == EWOULDBLOCK && rep) {
				if ((rep->r_flags & R_SOFTTERM)) {
					NFSTOKERNEL;
					return (EINTR);
				}
				/*
				 * If we have been waiting more than
				 * NFS_MAXRCVTIME seconds, force a reconnect.
				 */
				NFSGETNANOTIME(&mytime);
				if (((u_int32_t)mytime.tv_sec - rcvtime) >
				    NFS_MAXRCVTIMEO &&
				    ((u_int32_t)mytime.tv_sec - rcvtime) <
				    100000) {
					error = EPIPE;
				}
			   }
			} while (error == EWOULDBLOCK);
			NFSTOKERNEL;
			if (!error && auio.uio_resid > 0) {
			    if (rep->r_nmp != NULL)
#ifdef __FreeBSD__
			      log(LOG_INFO,
				 "short receive (%zd/%zd) from nfs server %s\n",
				 sizeof(u_int32_t) - auio.uio_resid,
				 sizeof(u_int32_t),
				 rep->r_nmp->nm_mountp->mnt_stat.f_mntfromname);
#else
			      log(LOG_INFO,
				 "short receive (%d/%d) from nfs server %s\n",
				 sizeof(u_int32_t) - auio.uio_resid,
				 sizeof(u_int32_t),
				 rep->r_nmp->nm_mountp->mnt_stat.f_mntfromname);
#endif
			    error = EPIPE;
			}
			if (error)
				goto errout;
			len = ntohl(len) & ~0x80000000;
DEBUG2PRINTF("nfs_rcv len=%d\n",len);
			/*
			 * This is SERIOUS! We are out of sync with the sender
			 * and forcing a disconnect/reconnect is all I can do.
			 */
			if (len > NFS_MAXPACKET) {
			    if (rep->r_nmp != NULL)
			      log(LOG_ERR, "%s (%d) from nfs server %s\n",
				"impossible packet length",
				len,
				rep->r_nmp->nm_mountp->mnt_stat.f_mntfromname);
			    error = EFBIG;
			    goto errout;
			}
			auio.uio_resid = len;
			NFSGETNANOTIME(&mytime);
			rcvtime = (u_int32_t)mytime.tv_sec;
			NFSTONETWORK;
			do {
			    rcvflg = MSG_WAITALL;
			    NFSSOCKSORCV(so, NULL, &auio, mp, NULL,
				&rcvflg, error);
			    /*
			     * If we have been waiting more than
			     * NFS_MAXRCVTIME seconds, force a reconnect.
			     */
			    NFSGETNANOTIME(&mytime);
			    if (((u_int32_t)mytime.tv_sec - rcvtime) >
				NFS_MAXRCVTIMEO &&
				((u_int32_t)mytime.tv_sec - rcvtime) <
				100000) {
				error = EPIPE;
			    }
			} while (error == EWOULDBLOCK || error == EINTR ||
			    error == ERESTART);
			NFSTOKERNEL;
			if (!error && auio.uio_resid > 0) {
			    if (rep->r_nmp != NULL)
			      log(LOG_INFO,
				"short receive (%d/%d) from nfs server %s\n",
				len - auio.uio_resid, len,
				rep->r_nmp->nm_mountp->mnt_stat.f_mntfromname);
			    error = EPIPE;
			}
		} else {
			/*
			 * NB: Since uio_resid is big, MSG_WAITALL is ignored
			 * and NFSSOCKSORCV() will return when it has either a
			 * control msg or a data msg.
			 * We have no use for control msg., but must grab them
			 * and then throw them away so we know what is going
			 * on.
			 */
			auio.uio_resid = len = 100000000; /* Anything Big */
			NFSUIOPROC(&auio, p);
			NFSTONETWORK;
			do {
			    rcvflg = 0;
			    NFSSOCKSORCV(so, NULL, &auio, mp,
				&control, &rcvflg, error);
			    if (control)
				mbuf_freem(control);
			    if (error == EWOULDBLOCK && rep) {
				if (rep->r_flags & R_SOFTTERM) {
					NFSTOKERNEL;
					return (EINTR);
				}
			    }
			} while (error == EWOULDBLOCK ||
				 (!error && *mp == NULL && control));
			NFSTOKERNEL;
			if ((rcvflg & MSG_EOR) == 0)
				printf("Egad!!\n");
			if (!error && *mp == NULL)
				error = EPIPE;
			len -= auio.uio_resid;
		}
errout:
		if (error && error != EINTR && error != ERESTART) {
			mbuf_freem(*mp);
			*mp = NULL;
			if (error != EPIPE && rep->r_nmp != NULL)
				log(LOG_INFO,
				    "receive error %d from nfs server %s\n",
				    error,
				 rep->r_nmp->nm_mountp->mnt_stat.f_mntfromname);
			error = newnfs_sndlock(&rep->r_nrp->nr_lock, rep);
			if (!error) {
				error = nfs_reconnect(rep, cred);
DEBUG1PRINTF("reconn1 err=%d\n",error);
				if (error)
					newnfs_sndunlock(&rep->r_nrp->nr_lock);
			}
			if (!error)
				goto tryagain;
		}
	} else {
		if ((so = rep->r_nrp->nr_so) == NULL)
			return (EACCES);
		if (so->so_state & SS_ISCONNECTED)
			getnam = NULL;
		else
			getnam = aname;
		auio.uio_resid = len = 1000000;
		NFSUIOPROC(&auio, p);
		NFSTONETWORK;
		do {
			rcvflg = 0;
			NFSSOCKSORCV(so, getnam, &auio, mp, NULL,
				&rcvflg, error);
			if (error == EWOULDBLOCK &&
			    (rep->r_flags & R_SOFTTERM)) {
				NFSTOKERNEL;
				return (EINTR);
			}
		} while (error == EWOULDBLOCK);
		NFSTOKERNEL;
		len -= auio.uio_resid;
	}
	if (error) {
		mbuf_freem(*mp);
		*mp = NULL;
	}
	/*
	 * Search for any mbufs that are not a multiple of 4 bytes long
	 * or with m_data not longword aligned.
	 * These could cause pointer alignment problems, so copy them to
	 * well aligned mbufs.
	 */
	newnfs_realign(mp, 5 * NFSX_UNSIGNED);
	return (error);
}

/*
 * Implement receipt of reply on a socket.
 * We must search through the list of received datagrams matching them
 * with outstanding requests using the xid, until ours is found.
 */
/* ARGSUSED */
static int
nfs_reply(struct nfsreq *myrep, struct nfsrv_descript *nd,
    struct ucred *cred, NFSPROC_T *p)
{
	struct nfsreq *rep;
	struct nfssockreq *nrp = myrep->r_nrp;
	int32_t t1;
	struct nfsmount *nmp;
	NFSSOCKADDR_T nam;
	u_int32_t rxid, *tl;
	int error;

	/*
	 * Loop around until we get our own reply
	 */
	for (;;) {
		/*
		 * Lock against other receivers so that I don't get stuck in
		 * sbwait() after someone else has received my reply for me.
		 * Also necessary for connection based protocols to avoid
		 * race conditions during a reconnect.
		 */
		error = nfs_rcvlock(myrep);
DEBUG2PRINTF("reply rcvlock err=%d\n",error);
		if (error)
			return (error);
		/* Already received, bye bye */
		if (myrep->r_mrep != NULL) {
			nfs_rcvunlock(&nrp->nr_lock);
			nd->nd_mrep = myrep->r_mrep;
			nd->nd_md = myrep->r_md;
			nd->nd_dpos = myrep->r_dpos;
DEBUG2PRINTF("reply done\n");
			return (0);
		}
		/*
		 * Get the next Rpc reply off the socket
		 */
		error = nfs_receive(myrep, &nam, &nd->nd_mrep, cred, p);
		nfs_rcvunlock(&nrp->nr_lock);
DEBUG2PRINTF("aft nfs_rcv err=%d\n",error);
		if (error) {
			/*
			 * Ignore routing errors on connectionless protocols?
			 */
			if (error != EACCES &&
			    NFSIGNORE_SOERROR(nrp->nr_soflags, error)) {
				nrp->nr_so->so_error = 0;
				if (myrep->r_flags & R_GETONEREP)
					return (0);
				continue;
			}
			return (error);
		}
		if (nam)
			NFSSOCKADDRFREE(nam);
		if (nd->nd_mrep == NULL) {
DEBUG2PRINTF("rcv null rflg=0x%x\n",myrep->r_flags);
			if (myrep->r_flags & R_SOFTTERM)
				return (EINTR);
			continue;
		}
	
		/*
		 * Get the xid and check that it is an rpc reply
		 */
		nd->nd_md = nd->nd_mrep;
		nd->nd_dpos = NFSMTOD(nd->nd_md, caddr_t);
DEBUG2PRINTF("at dissect\n");
		NFSM_DISSECT(tl, u_int32_t *, 2 * NFSX_UNSIGNED);
DEBUG2PRINTF("aft dissect\n");
		rxid = *tl++;
DEBUG2PRINTF("rxid=0x%x\n",rxid);
		if (*tl != newrpc_reply) {
DEBUG2PRINTF("not reply\n");
			NFSINCRGLOBAL(newnfsstats.rpcinvalid);
			mbuf_freem(nd->nd_mrep);
nfsmout:
			if (myrep->r_flags & R_GETONEREP)
				return (0);
			continue;
		}

		/*
		 * Loop through the request list to match up the reply
		 * Iff no match, just drop the datagram
		 */
DEBUG2PRINTF("reply tailq\n");
		TAILQ_FOREACH(rep, &nfsd_reqq, r_chain) {
			if (rep->r_mrep == NULL && rxid == rep->r_xid) {
				if (nfstest_dropreplies) {
				    if ((arc4random() % nfstest_dropreplies) == 0) {
					rep = NULL;
					break;
				    }
				}
				/* Found it.. */
				rep->r_mrep = nd->nd_mrep;
				rep->r_md = nd->nd_md;
				rep->r_dpos = nd->nd_dpos;
				rep->r_flags &= ~R_SENT;
				if (rep->r_nmp != NULL) {
				    nmp = rep->r_nmp;
				    if (newnfsrtton) {
					struct rttl *rt;

					rt = &newnfsrtt.rttl[newnfsrtt.pos];
					rt->proc = rep->r_procnum;
					rt->rto = NFS_RTO(nmp, proct[rep->r_procnum]);
					rt->sent = nmp->nm_sent;
					rt->cwnd = nmp->nm_cwnd;
					rt->srtt = nmp->nm_srtt[proct[rep->r_procnum] - 1];
					rt->sdrtt = nmp->nm_sdrtt[proct[rep->r_procnum] - 1];
					rt->fsid = nmp->nm_mountp->mnt_stat.f_fsid;
					NFSGETTIME(&rt->tstamp);
					if (rep->r_flags & R_TIMING)
						rt->rtt = rep->r_rtt;
					else
						rt->rtt = 1000000;
					newnfsrtt.pos = (newnfsrtt.pos + 1) % NFSRTTLOGSIZ;
				    }
				    /*
				     * Update congestion window.
				     * Do the additive increase of
				     * one rpc/rtt.
				     */
				    if (nmp->nm_cwnd <= nmp->nm_sent) {
					nmp->nm_cwnd +=
					   (NFS_CWNDSCALE * NFS_CWNDSCALE +
					   (nmp->nm_cwnd >> 1)) / nmp->nm_cwnd;
					if (nmp->nm_cwnd > NFS_MAXCWND)
						nmp->nm_cwnd = NFS_MAXCWND;
				    }
				    nmp->nm_sent -= NFS_CWNDSCALE;
				    /*
				     * Update rtt using a gain of 0.125 on the mean
				     * and a gain of 0.25 on the deviation.
				     */
				    if (rep->r_flags & R_TIMING) {
					/*
					 * Since the timer resolution of
					 * NFS_HZ is so course, it can often
					 * result in r_rtt == 0. Since
					 * r_rtt == N means that the actual
					 * rtt is between N+dt and N+2-dt ticks,
					 * add 1.
					 */
					t1 = rep->r_rtt + 1;
					t1 -= (NFS_SRTT(rep) >> 3);
					NFS_SRTT(rep) += t1;
					if (t1 < 0)
						t1 = -t1;
					t1 -= (NFS_SDRTT(rep) >> 2);
					NFS_SDRTT(rep) += t1;
				    }
				    nmp->nm_timeouts = 0;
				}
				break;
			}
		}
		/*
		 * If not matched to a request, drop it.
		 * If it's mine, get out.
		 */
		if (rep == 0) {
			NFSINCRGLOBAL(newnfsstats.rpcunexpected);
			mbuf_freem(nd->nd_mrep);
		} else if (rep == myrep) {
			if (rep->r_mrep == NULL)
				panic("nfsreply nil");
DEBUG2PRINTF("reply done2\n");
			return (0);
		}
		if (myrep->r_flags & R_GETONEREP)
			return (0);
	}
}

/*
 * newnfs_request - goes something like this
 *	- fill in request struct
 *	- links it into list
 *	- calls newnfs_send() for first transmit
 *	- calls nfs_receive() to get reply
 *	- break down rpc header and return with nfs reply pointed to
 *	  by mrep or error
 * nb: always frees up mreq mbuf list
 */
int
newnfs_request(struct nfsrv_descript *nd, struct nfsmount *nmp,
    struct nfsclient *clp, struct nfssockreq *nrp, vnode_t vp,
    NFSPROC_T *p, struct ucred *cred, u_int32_t prog, u_int32_t vers,
    u_char *retsum, int toplevel, u_int64_t *xidp)
{
	mbuf_t m;
	struct nfsreq *rep;
	u_int32_t *tl;
	int i, j;
	u_char *cp;
	time_t waituntil;
	int s, error = 0, verf_type, trycnt, connrequired;
	u_int trylater_delay = 1;
	u_int32_t seqnum;

	if (xidp != NULL)
		*xidp = 0;
	/*
	 * Get the RPC header with authorization.
	 */
	MALLOC(rep, struct nfsreq *, sizeof(struct nfsreq), M_NFSDREQ,
	    M_WAITOK);
	SLIST_INIT(&rep->r_seqlist);
	if (nrp->nr_sotype == SOCK_STREAM)
		rep->r_flags = R_TCP;
	else
		rep->r_flags = 0;
	/*
	 * For a client side mount, nmp is != NULL and clp == NULL. For
	 * server calls (callbacks or upcalls), nmp == NULL.
	 */
	if (clp != NULL) {
		NFSLOCKSTATE;
		if (clp->lc_flags & LCL_GSS) {
			if (toplevel)
				rep->r_flags |= R_TOPLEVEL;
			rep->r_flags |= R_KERBV;
			nd->nd_flag |= ND_KERBV;
			if (nd->nd_procnum != NFSPROC_NULL) {
				if (clp->lc_flags & LCL_GSSINTEGRITY)
					rep->r_flags |= R_INTEGRITY;
				else if (clp->lc_flags & LCL_GSSPRIVACY)
					rep->r_flags |= R_PRIVACY;
			}
		}
		NFSUNLOCKSTATE;
	} else if (nmp != NULL && NFSHASKERB(nmp)) {
		if (toplevel)
			rep->r_flags |= R_TOPLEVEL;
		rep->r_flags |= R_KERBV;
		nd->nd_flag |= ND_KERBV;
		if (NFSHASALLGSSNAME(nmp))
			nd->nd_flag |= ND_USEGSSNAME;
		if (nd->nd_flag & ND_USEGSSNAME)
			rep->r_flags |= R_USEGSSNAME;
		if (nd->nd_procnum != NFSPROC_NULL) {
			if (NFSHASINTEGRITY(nmp))
				rep->r_flags |= R_INTEGRITY;
			else if (NFSHASPRIVACY(nmp))
				rep->r_flags |= R_PRIVACY;
		}
	}
	if (nmp != NULL)
		rep->r_flags |= R_CLIENTSIDE;
	rep->r_nmp = nmp;
	rep->r_clp = clp;
	rep->r_nrp = nrp;
	rep->r_procp = p;
	rep->r_handp = NULL;
	rep->r_handb = NULL;
	if (cred != NULL)
		rep->r_uid = cred->cr_uid;
	rep->r_procnum = nd->nd_procnum;
	if ((nd->nd_flag & ND_NFSV4) &&
	    nd->nd_procnum != NFSV4PROC_CBNULL &&
	    nd->nd_procnum != NFSV4PROC_CBCOMPOUND)
		rep->r_procnum = NFSV4PROC_COMPOUND;
	rep->r_prog = prog;
	rep->r_vers = vers;
	m = nd->nd_mreq;
	if (m)
		i = mbuf_len(m);
	else
		i = 0;
	while (mbuf_next(m)) {
		m = mbuf_next(m);
		i += mbuf_len(m);
	}
	rep->r_mrestend = m;
	rep->r_mrestlen = i;
	rep->r_mrest = nd->nd_mreq;
DEBUG1PRINTF("request mrest=%d\n",i);
	if (rep->r_flags & R_PRIVACY) {
		/*
		 * Paranoia: These should already be correctly set, but...
		 */
		nd->nd_mb = m;
		nd->nd_bpos = NFSMTOD(m, caddr_t) + mbuf_len(m);
		/*
		 * Build a pad trailer.
		 */
		i = 8 - ((i + 3 * NFSX_UNSIGNED) % 8);
		NFSM_BUILD(cp, u_char *, i);
		for (j = 0; j < i; j++)
			*cp++ = (u_char)i;
		rep->r_mrestlen += i;
DEBUG1PRINTF("aft pad=%d rep->r_mrestlen=%d\n",i,rep->r_mrestlen);
		i = nfsgss_lengthtok(nd, rep->r_mrestlen + 3 * NFSX_UNSIGNED);
		i = NFSM_RNDUP(i) - i;
		if (i > 0) {
DEBUG1PRINTF("priv xdrpad i=%d\n",i);
			NFSM_BUILD(cp, u_char *, i);
			for (j = 0; j < i; j++)
				*cp++ = 0x0;
		}
		rep->r_mrestend = nd->nd_mb;
	}

	trycnt = 0;
tryagain:
	newnfs_rpchead(cred, rep);
	if (xidp != NULL)
		/*
		 * I have no idea what Darwin uses this for, since the
		 * order in which RPCs are generated for a server does
		 * not imply that the RPCs will be performed in that order
		 * at the server, but here it is.
		 */
		*xidp = (((u_int64_t)newnfs_xidwrap) << 32) |
		    ((u_int64_t)fxdr_unsigned(u_int32_t, rep->r_xid));
	if (rep->r_flags & (R_AUTHERR | R_SOFTTERM)) {
		if ((rep->r_flags & R_TOPLEVEL) && rep->r_handp != NULL)
			nfsgsscl_releasehandle(rep, 0);
		mbuf_freem(rep->r_mrest);
		nfsreq_free(rep);
		if (rep->r_flags & R_AUTHERR)
			return (EAUTH);
		return (EINTR);
	}

	/*
	 * and away we go, sending 1, 2, 3...
	 */
	if (nmp == NULL) {
		/*
		 * Three cases:
		 * - Null RPC callback to client
		 * - Non-Null RPC callback to client, wait a little longer
		 * - upcalls to nfsuserd and gssd (clp == NULL)
		 */
		if (clp == NULL) {
			rep->r_retry = NFSV4_UPCALLRETRY;
		} else {
			if (rep->r_procnum != NFSPROC_NULL)
				rep->r_retry = NFSV4_CALLBACKRETRY * 3;
			else
				rep->r_retry = NFSV4_CALLBACKRETRY;
		}
	} else if (NFSHASSOFT(nmp)) {
		rep->r_retry = nmp->nm_retry;
	} else {
		rep->r_retry = NFS_MAXREXMIT + 1;	/* past clip limit */
	}
	rep->r_rtt = rep->r_rexmit = 0;
	if (nmp && proct[rep->r_procnum] > 0)
		rep->r_flags |= R_TIMING;
	rep->r_mrep = NULL;

	/*
	 * Do the client side RPC.
	 */
	if (nmp != NULL)
		NFSINCRGLOBAL(newnfsstats.rpcrequests);
	/*
	 * Chain request into list of outstanding requests. Be sure
	 * to put it LAST so timer finds oldest requests first.
	 */
DEBUG2PRINTF("newnfs Req atQ err=%d\n",error);
	s = splsoftclock();
	NFSLOCKREQ();
	TAILQ_INSERT_TAIL(&nfsd_reqq, rep, r_chain);
	NFSUNLOCKREQ();

	/*
	 * If backing off another request or avoiding congestion, don't
	 * send this one now but let timer do it. If not timing a request,
	 * do it now.
	 */
	if (nrp->nr_so && (nrp->nr_sotype != SOCK_DGRAM || nmp == NULL ||
		NFSHASDUMBTIMR(nmp) || nmp->nm_sent < nmp->nm_cwnd)) {
		splx(s);
		connrequired = (nrp->nr_sotype == SOCK_STREAM);
		if (connrequired)
			error = newnfs_sndlock(&nrp->nr_lock, rep);
		if (!error) {
			error = newnfs_send(nrp->nr_so, nrp->nr_nam,
			    m_copym(rep->r_mreq, 0, M_COPYALL, M_WAIT),
			    rep, p);
			if (connrequired)
				newnfs_sndunlock(&nrp->nr_lock);
		}
if (error) printf("send err=%d\n",error);
DEBUG2PRINTF("aft send err=%d\n",error);
		if (error == 0 && (rep->r_flags & R_MUSTRESEND) == 0) {
			if (nmp != NULL)
				nmp->nm_sent += NFS_CWNDSCALE;
			rep->r_flags |= R_SENT;
		}
	} else {
		splx(s);
		rep->r_rtt = -1;
	}

	/*
	 * Wait for the reply from our send or the timer's.
	 */
	if (error == 0 || error == EPIPE)
		error = nfs_reply(rep, nd, cred, p);

	/*
	 * RPC done, unlink the request.
	 */
	s = splsoftclock();
	NFSLOCKREQ();
	TAILQ_REMOVE(&nfsd_reqq, rep, r_chain);
	NFSUNLOCKREQ();
	splx(s);

	/*
	 * Decrement the outstanding request count.
	 */
	if (rep->r_flags & R_SENT) {
		rep->r_flags &= ~R_SENT;	/* paranoia */
		if (nmp != NULL)
			nmp->nm_sent -= NFS_CWNDSCALE;
	}

	/*
	 * Update seqnum window.
	 */
	if (rep->r_flags & R_GSSGOTSEQ) {
		rep->r_flags &= ~R_GSSGOTSEQ;
		nfsgss_rcvdclseq(rep);
	}

	/*
	 * If there was a successful reply and a tprintf msg.
	 * tprintf a response.
	 */
	if (nmp != NULL && error == 0 && (rep->r_flags & R_TPRINTFMSG))
		nfs_msg(rep->r_procp, nmp->nm_mountp->mnt_stat.f_mntfromname,
		    "is alive again");
DEBUG2PRINTF("newnfs_request at dissect err=%d\n",error);
	if (error) {
		if (rep->r_flags & R_TOPLEVEL)
			nfsgsscl_releasehandle(rep, 0);
		mbuf_freem(rep->r_mreq);
		nfsreq_free(rep);
		return (error);
	}

	/*
	 * break down the rpc header and check if ok
	 */
	NFSM_DISSECT(tl, u_int32_t *, 3 * NFSX_UNSIGNED);
	if (*tl++ == newrpc_msgdenied) {
DEBUG2PRINTF("msg denied\n");
		if (*tl == newrpc_mismatch) {
			error = EOPNOTSUPP;
		} else if (*tl == newrpc_autherr) {
			i = fxdr_unsigned(int, *++tl);
DEBUG2PRINTF("autherr=%d\n",i);
			if ((i == AUTH_PROBCRED || i == AUTH_CTXCRED) &&
			    (rep->r_flags & R_TOPLEVEL) && ++trycnt < 4) {
				/*
				 * Try again after discarding failed handle.
				 */
				if ((rep->r_flags & R_PRIVACY) &&
				    rep->r_procnum != NFSPROC_NULL)
				    (void) nfsgss_des(rep->r_startm,
					rep->r_startpos, rep->r_mrestlen +
					3 * NFSX_UNSIGNED, rep->r_key, 0);
				mbuf_freem(rep->r_mrep);
				mbuf_setnext(rep->r_mheadend, NULL);
				mbuf_freem(rep->r_mreq);
				if (mbuf_next(rep->r_mrestend)) {
					mbuf_freem(mbuf_next(rep->r_mrestend));
					mbuf_setnext(rep->r_mrestend, NULL);
				}
				nd->nd_repstat = 0;
				nfsgsscl_zaphandle(rep);
				nfsgsscl_releasehandle(rep, 0);
				rep->r_handp = NULL;
DEBUG1PRINTF("try again\n");
				goto tryagain;
			}
			error = EAUTH;
		} else {
			error = EACCES;
		}
		if (rep->r_flags & R_TOPLEVEL)
			nfsgsscl_releasehandle(rep, 0);
		mbuf_freem(rep->r_mrep);
		mbuf_freem(rep->r_mreq);
		nfsreq_free(rep);
DEBUG2PRINTF("req msg denied val=0x%x err=%d\n",*tl,error);
		return (error);
	}

	/*
	 * Grab any Kerberos verifier, otherwise just throw it away.
	 */
	verf_type = fxdr_unsigned(int, *tl++);
	i = fxdr_unsigned(int, *tl);
	if (verf_type == RPCAUTH_GSS) {
		error = nfsgss_verf(nd, rep->r_handp->nfsh_sched,
		    rep->r_seqnum, rep, i, retsum);
		/*
		 * AUTH_PROBCRED indicates that the verifier has failed.
		 * For UDP, this can be the result of an old seq# that
		 * wasn't kept, so set NFSERR_DELAY to get it to try again.
		 */
		if (error == AUTH_PROBCRED && (rep->r_flags & R_TCP) == 0) {
			nd->nd_repstat = NFSERR_DELAY;
			error = 0;
		}
	} else if (i > 0) {
		error = nfsm_advance(nd, NFSM_RNDUP(i), -1);
	}
	if (error) {
DEBUG1PRINTF("reply err=%d\n",error);
		goto nfsmout;
	}
	NFSM_DISSECT(tl, u_int32_t *, NFSX_UNSIGNED);
	/* 0 == ok */
	if (*tl == 0) {
DEBUG2PRINTF("req rep ok ndflag=0x%x proc=%d\n",nd->nd_flag,nd->nd_procnum);
	    if (nd->nd_procnum != NFSPROC_NULL) {
		/*
		 * Any nfs proc except Null.
		 */
		if (rep->r_flags & R_INTEGRITY) {
DEBUG1PRINTF("reply integ\n");
		    NFSM_DISSECT(tl, u_int32_t *, NFSX_UNSIGNED);
		    j = fxdr_unsigned(int, *tl);
		    if (j < NFSX_UNSIGNED) {
			error = EBADRPC;
			goto nfsmout;
		    }
		    nd->nd_startm = nd->nd_md;
		    nd->nd_startpos = nd->nd_dpos;
		    error = nfsm_advance(nd, j, -1);
DEBUG1PRINTF("adv err=%d\n",error);
		    if (error)
			goto nfsmout;
		    NFSM_DISSECT(tl, u_int32_t *, NFSX_UNSIGNED);
		    i = fxdr_unsigned(int, *tl);
		    i = nfsgss_ckheader(nd, i, j, 1, rep->r_handp,
			rep->r_handp->nfsh_sched);
		    if (i) {
DEBUG1PRINTF("ckhdr failed=%d\n",i);
			error = EAUTH;
			goto nfsmout;
		    }
		    nd->nd_md = nd->nd_startm;
		    nd->nd_dpos = nd->nd_startpos;
DEBUG1PRINTF("eo reply integ\n");
		} else if (rep->r_flags & R_PRIVACY) {
DEBUG1PRINTF("reply priv\n");
		    NFSM_DISSECT(tl, u_int32_t *, NFSX_UNSIGNED);
		    j = fxdr_unsigned(int, *tl);
DEBUG1PRINTF("priv datalen=%d\n",j);
		    if (j < GSSX_MINWRAP) {
			error = EBADRPC;
			goto nfsmout;
		    }
		    i = nfsgss_unwrap(nd, j, 1, rep->r_handp->nfsh_key,
			rep->r_handp->nfsh_sched);
DEBUG1PRINTF("aft unwrap ret=%d\n",i);
		    if (i) {
			error = EAUTH;
			goto nfsmout;
		    }
		}
		if (rep->r_flags & (R_INTEGRITY | R_PRIVACY)) {
		    NFSM_DISSECT(tl, u_int32_t *, NFSX_UNSIGNED);
		    seqnum = fxdr_unsigned(u_int32_t, *tl);
DEBUG1PRINTF("rep seq=%d fseq=%d\n",seqnum,rep->r_seqnum);
		    if (seqnum != rep->r_seqnum) {
DEBUG1PRINTF("bad seqnum\n");
			if ((rep->r_flags & R_TCP) == 0) {
			    nd->nd_repstat = NFSERR_DELAY;
			} else {
			    error = EAUTH;
			    goto nfsmout;
			}
		    }
		}

		/*
		 * and now the actual NFS xdr.
		 */
		NFSM_DISSECT(tl, u_int32_t *, NFSX_UNSIGNED);
		if (nd->nd_repstat != NFSERR_DELAY)
			nd->nd_repstat = fxdr_unsigned(u_int32_t, *tl);
		if (nd->nd_repstat != 0) {
			if ((nd->nd_repstat == NFSERR_DELAY &&
			     (nd->nd_flag & ND_NFSV4) &&
			     nd->nd_procnum != NFSPROC_OPEN &&
			     nd->nd_procnum != NFSPROC_CREATE &&
			     nd->nd_procnum != NFSPROC_OPENCONFIRM &&
			     nd->nd_procnum != NFSPROC_OPENDOWNGRADE &&
			     nd->nd_procnum != NFSPROC_CLOSE &&
			     nd->nd_procnum != NFSPROC_LOCK &&
			     nd->nd_procnum != NFSPROC_LOCKU) ||
			    (nd->nd_repstat == NFSERR_DELAY &&
			     (nd->nd_flag & ND_NFSV4) == 0) ||
			    nd->nd_repstat == NFSERR_RESOURCE) {
				if (rep->r_flags & R_TOPLEVEL) {
					nfsgsscl_releasehandle(rep, 0);
					rep->r_handp = NULL;
				}
				if ((rep->r_flags & R_PRIVACY) &&
				    rep->r_procnum != NFSPROC_NULL)
				    (void) nfsgss_des(rep->r_startm,
					rep->r_startpos, rep->r_mrestlen +
					3 * NFSX_UNSIGNED, rep->r_key, 0);
				mbuf_freem(rep->r_mrep);
				mbuf_setnext(rep->r_mheadend, NULL);
				mbuf_freem(rep->r_mreq);
				if (mbuf_next(rep->r_mrestend)) {
					mbuf_freem(mbuf_next(rep->r_mrestend));
					mbuf_setnext(rep->r_mrestend, NULL);
				}
				nd->nd_repstat = 0;
				if (trylater_delay > NFS_TRYLATERDEL)
					trylater_delay = NFS_TRYLATERDEL;
				waituntil = NFSD_MONOSEC + trylater_delay;
				while (NFSD_MONOSEC < waituntil)
					(void) tsleep((caddr_t)&lbolt,
						PSOCK, "nfstry", 0);
				trylater_delay *= 2;
DEBUG2PRINTF("try again2\n");
				goto tryagain;
			}

			/*
			 * If the File Handle was stale, invalidate the
			 * lookup cache, just in case.
			 */
			if (nd->nd_repstat == ESTALE && vp)
				cache_purge(vp);
		}

		/*
		 * Get rid of the tag, return count, and PUTFH result for V4.
		 */
		if (nd->nd_flag & ND_NFSV4) {
			NFSM_DISSECT(tl, u_int32_t *, NFSX_UNSIGNED);
			i = fxdr_unsigned(int, *tl);
			error = nfsm_advance(nd, NFSM_RNDUP(i), -1);
			if (error)
				goto nfsmout;
			NFSM_DISSECT(tl, u_int32_t *, 3 * NFSX_UNSIGNED);
			i = fxdr_unsigned(int, *++tl);

			/*
			 * If the first op's status is non-zero, mark that
			 * there is no more data to process.
			 */
			if (*++tl)
				nd->nd_flag |= ND_NOMOREDATA;

			/*
			 * If the first op is Putfh, throw its results away
			 * and toss the op# and status for the first op.
			 */
			if (nmp && i == NFSV4OP_PUTFH && !(*tl)) {
				NFSM_DISSECT(tl,u_int32_t *,2 * NFSX_UNSIGNED);
				i = fxdr_unsigned(int, *tl++);
				j = fxdr_unsigned(int, *tl);
				/*
				 * All Compounds that do an Op that must
				 * be in sequence consist of NFSV4OP_PUTFH
				 * followed by one of these. As such, we
				 * can determine if the seqid# should be
				 * incremented, here.
				 */
				if ((i == NFSV4OP_OPEN ||
				     i == NFSV4OP_OPENCONFIRM ||
				     i == NFSV4OP_OPENDOWNGRADE ||
				     i == NFSV4OP_CLOSE ||
				     i == NFSV4OP_LOCK ||
				     i == NFSV4OP_LOCKU) &&
				    (j == 0 ||
				     (j != NFSERR_STALECLIENTID &&
				      j != NFSERR_STALESTATEID &&
				      j != NFSERR_BADSTATEID &&
				      j != NFSERR_BADSEQID &&
				      j != NFSERR_BADXDR &&	 
				      j != NFSERR_RESOURCE &&
				      j != NFSERR_NOFILEHANDLE)))		 
					nd->nd_flag |= ND_INCRSEQID;
				/*
				 * If the first op's status is non-zero, mark
				 * that there is no more data to process.
				 */
				if (j)
					nd->nd_flag |= ND_NOMOREDATA;
			}

			/*
			 * If R_DONTRECOVER is set, replace the stale error
			 * reply, so that recovery isn't initiated.
			 */
			if ((nd->nd_repstat == NFSERR_STALECLIENTID ||
			     nd->nd_repstat == NFSERR_STALESTATEID) &&
			    (rep->r_flags & R_DONTRECOVER))
				nd->nd_repstat = NFSERR_STALEDONTRECOVER;
		}
	    }

	    if (rep->r_flags & R_TOPLEVEL)
		nfsgsscl_releasehandle(rep, 0);
	    mbuf_freem(rep->r_mreq);
	    nfsreq_free(rep);
	    return (0);
	}
DEBUG1PRINTF("accept stat=%d\n",fxdr_unsigned(int, *tl));
	error = EPROTONOSUPPORT;
nfsmout:
	if (rep->r_flags & R_TOPLEVEL)
		nfsgsscl_releasehandle(rep, 0);
	mbuf_freem(rep->r_mrep);
	mbuf_freem(rep->r_mreq);
	nfsreq_free(rep);
	return (error);
}

/*
 * Nfs timer routine
 * Scan the nfsreq list and retranmit any requests that have timed out
 * To avoid retransmission attempts on STREAM sockets (in the future) make
 * sure to set the r_retry field to 0 (implies nm_retry == 0).
 */
void
newnfs_timer(void *arg)
{
	struct nfsreq *rep;
	mbuf_t m;
	struct socket *so;
	struct nfsmount *nmp;
	struct nfssockreq *nrp;
	int timeo;
	int s, error;
#ifdef __FreeBSD__
	static time_t lasttime = 0;
#endif

	s = NFSSPLNET();
	TAILQ_FOREACH(rep, &nfsd_reqq, r_chain) {
		nmp = rep->r_nmp;
		nrp = rep->r_nrp;
		if (rep->r_mrep || (rep->r_flags & R_SOFTTERM))
			continue;
		if (newnfs_sigintr(nmp, rep, rep->r_procp)) {
			rep->r_flags |= R_SOFTTERM;
			continue;
		}
		if (rep->r_rtt >= 0) {
			rep->r_rtt++;
			if (nmp == NULL) {
				if (rep->r_clp == NULL)
					timeo = NFSV4_UPCALLTIMEO;
				else
					timeo = NFSV4_CALLBACKTIMEO;
			} else if (nrp->nr_sotype != SOCK_DGRAM) {
				timeo = NFS_TCPTIMEO;
			} else {
			    if (NFSHASDUMBTIMR(nmp))
				timeo = nmp->nm_timeo;
			    else
				timeo = NFS_RTO(nmp, proct[rep->r_procnum]);
			    if (nfstest_nobackoff)
				nmp->nm_timeouts = 0;
			    if (nmp->nm_timeouts > 0)
				timeo *= nfs_backoff[nmp->nm_timeouts - 1];
			}
			if (rep->r_rtt <= timeo)
				continue;
			if (nmp && nmp->nm_timeouts < 8)
				nmp->nm_timeouts++;
		}
		/*
		 * Check for server not responding
		 */
		if (nmp && (rep->r_flags & R_TPRINTFMSG) == 0 &&
		     rep->r_rexmit > 8) {
			nfs_msg(rep->r_procp,
			    nmp->nm_mountp->mnt_stat.f_mntfromname,
			    "not responding");
			rep->r_flags |= R_TPRINTFMSG;
		}
		if (rep->r_rexmit >= rep->r_retry) {	/* too many */
			/*
			 * This only happens for soft mounts and callbacks,
			 * since the r_retry value is NFS_MAXREXMIT + 1 for hard
			 * mounts and r_rexmit never exceeds NFS_MAXREXMIT.
			 */
			if (nmp)
				NFSINCRGLOBAL(newnfsstats.rpctimeouts);
			rep->r_flags |= R_SOFTTERM;
			continue;
		}

		/*
		 * For callbacks and NFS V4 over reliable transport, never
		 * do retries.
		 * (I felt this was correct for NFS over TCP, but some servers
		 *  do throw away requests, so a long timeout/retransmit is
		 *  required for V2,3 NFS over TCP.)
		 */
		if (nrp->nr_sotype != SOCK_DGRAM &&
		    (!nmp || NFSHASNFSV4(nmp))) {
			/*
			 * Increment r_rexmit here, since retries never
			 * actually occur, but soft mounts and callbacks
			 * do need to "timeout".
			 */
			if (++rep->r_rexmit > NFS_MAXREXMIT)
				rep->r_rexmit = NFS_MAXREXMIT;
			continue;
		}

		if ((so = nrp->nr_so) == NULL)
			continue;

		/*
		 * If there is enough space and the window allows..
		 *	Resend it
		 * Set r_rtt to -1 in case we fail to send it now.
		 */
		rep->r_rtt = -1;
		if (nrp->nr_sotype != SOCK_DGRAM) {
			/*
			 * Must get the send lock without waiting.
			 */
			if (nrp->nr_lock & NFSR_SNDLOCK)
				continue;
			nrp->nr_lock |= NFSR_SNDLOCK;
		}
		if (nfs_sbspace(&so->so_snd) >= rep->r_mreq->m_pkthdr.len &&
		   (!nmp || NFSHASDUMBTIMR(nmp) ||
		    (nrp->nr_sotype != SOCK_DGRAM) ||
		    (rep->r_flags & R_SENT) ||
		    nmp->nm_sent < nmp->nm_cwnd) &&
		   (m = m_copym(rep->r_mreq, 0, M_COPYALL, M_DONTWAIT))){
			NFSTONETWORK;
			if (so->so_state & SS_ISCONNECTED)
			    error = NFSSOCKSND(so, m, NULL);
			else
			    error = NFSSOCKSND(so, m, nrp->nr_nam);
			NFSTOKERNEL;
			if (error) {
				if (NFSIGNORE_SOERROR(nrp->nr_soflags, error))
					so->so_error = 0;
			} else {
				if (++rep->r_rexmit > NFS_MAXREXMIT)
					rep->r_rexmit = NFS_MAXREXMIT;
				rep->r_rtt = 0;
				/*
				 * Iff first send, start timing
				 * else turn timing off, backoff timer
				 * and divide congestion window by 2.
				 */
				if (nmp) {
				    if (rep->r_flags & R_SENT) {
					rep->r_flags &= ~R_TIMING;
					nmp->nm_cwnd >>= 1;
					if (nmp->nm_cwnd < NFS_CWNDSCALE)
						nmp->nm_cwnd = NFS_CWNDSCALE;
					NFSINCRGLOBAL(newnfsstats.rpcretries);
				    } else {
					rep->r_flags |= R_SENT;
					nmp->nm_sent += NFS_CWNDSCALE;
				    }
				}
			}
		}
		if (nrp->nr_sotype != SOCK_DGRAM)
			newnfs_sndunlock(&nrp->nr_lock);
	}

#ifdef __FreeBSD__
	/*
	 * Call the server timer.
	 * The argument indicates if it is the next second and therefore
	 * leases should be checked.
	 */
	if (lasttime != NFSD_MONOSEC) {
		lasttime = NFSD_MONOSEC;
		nfsrv_servertimer();
	}
#endif /* __FreeBSD__ */
	splx(s);
	NFS_TIMERSETUP(nfscl_ticks, arg);
}

/*
 * Lock a socket against others.
 * Necessary for STREAM sockets to ensure you get an entire rpc request/reply
 * and also to avoid race conditions between the processes with nfs requests
 * in progress when a reconnect is necessary.
 */
int
newnfs_sndlock(int *flagp, struct nfsreq *rep)
{
	NFSPROC_T *p;
	int slptimeo = 0;
	struct nfsmount *nmp;
	struct timespec ts;

	NFSLOCKSOCK;
	while (*flagp & NFSR_SNDLOCK) {
		if (rep) {
			p = rep->r_procp;
			nmp = rep->r_nmp;
			if (nmp && NFSHASINTORSOFT(nmp))
				slptimeo = 2;
		} else {
			p = NULL;
			nmp = NULL;
		}
		if (newnfs_sigintr(nmp, rep, p)) {
			NFSUNLOCKSOCK;
			return (EINTR);
		}
		*flagp |= NFSR_WANTSND;
		ts.tv_sec = slptimeo;
		ts.tv_nsec = 0;
		(void) nfsmsleep((caddr_t)flagp, NFSSOCKMUTEXPTR,
		    PZERO - 1, "nfsndlck", &ts);
	}
	*flagp |= NFSR_SNDLOCK;
	NFSUNLOCKSOCK;
	return (0);
}

/*
 * Unlock the stream socket for others.
 */
void
newnfs_sndunlock(int *flagp)
{

	NFSLOCKSOCK;
	if ((*flagp & NFSR_SNDLOCK) == 0)
		panic("nfs sndunlock");
	*flagp &= ~NFSR_SNDLOCK;
	if (*flagp & NFSR_WANTSND) {
		*flagp &= ~NFSR_WANTSND;
		wakeup((caddr_t)flagp);
	}
	NFSUNLOCKSOCK;
}

static int
nfs_rcvlock(struct nfsreq *rep)
{
	int *flagp = &rep->r_nrp->nr_lock;
	int slpflag, slptimeo = 0;
	struct timespec ts;

	NFSLOCKSOCK;
	if (rep->r_nmp && NFSHASINT(rep->r_nmp))
		slpflag = PCATCH;
	else
		slpflag = 0;
	while (*flagp & NFSR_RCVLOCK) {
		if (newnfs_sigintr(rep->r_nmp, rep, rep->r_procp)) {
			NFSUNLOCKSOCK;
			return (EINTR);
		}
		*flagp |= NFSR_WANTRCV;
		ts.tv_sec = slptimeo;
		ts.tv_nsec = 0;
		(void) nfsmsleep((caddr_t)flagp, NFSSOCKMUTEXPTR,
		    slpflag | (PZERO - 1), "nfsrcvlk", &ts);
		if (slpflag == PCATCH) {
			slpflag = 0;
			slptimeo = 2;
		}
	}
	*flagp |= NFSR_RCVLOCK;
	NFSUNLOCKSOCK;
	return (0);
}

/*
 * Unlock the stream socket for others.
 */
static void
nfs_rcvunlock(int *flagp)
{

	NFSLOCKSOCK;
	if ((*flagp & NFSR_RCVLOCK) == 0)
		panic("nfs rcvunlock");
	*flagp &= ~NFSR_RCVLOCK;
	if (*flagp & NFSR_WANTRCV) {
		*flagp &= ~NFSR_WANTRCV;
		wakeup((caddr_t)flagp);
	}
	NFSUNLOCKSOCK;
}
#endif	/* !APPLE */

/*
 * Free up an nfsreq structure.
 */
APPLESTATIC void
nfsreq_free(struct nfsreq *rep)
{
	struct nfsreqseq *rsp, *nrsp;

	rsp = SLIST_FIRST(&rep->r_seqlist);
	while (rsp != SLIST_END(&rep->r_seqlist)) {
		nrsp = SLIST_NEXT(rsp, rs_next);
		FREE((caddr_t)rsp, M_NFSRGSSSEQ);
		rsp = nrsp;
	}
	FREE((caddr_t)rep, M_NFSDREQ);
}

/*
 * Initialize the reply header data structures.
 */
APPLESTATIC void
nfsrvd_rephead(struct nfsrv_descript *nd)
{
	mbuf_t mreq;

	/*
	 * If this is a big reply, use a cluster
	 * else leave space for GSS prepends.
	 */
	if ((nd->nd_flag & (ND_GSSINTEGRITY | ND_GSSINITREPLY)) ==
	    ND_GSSINTEGRITY) {
		NFSMGET(mreq);
		nd->nd_mreq = mreq;
		nd->nd_mb = mreq;
		NFSM_DATAP(mreq, NFSX_UNSIGNED);
	} else if ((nd->nd_flag & (ND_GSSPRIVACY | ND_GSSINITREPLY)) ==
	    ND_GSSPRIVACY) {
		NFSMGET(mreq);
		nd->nd_mreq = mreq;
		nd->nd_mb = mreq;
		NFSM_DATAP(mreq, 3 * NFSX_UNSIGNED);
	} else if ((nd->nd_flag & ND_GSSINITREPLY) == 0 &&
		nfs_bigreply[nd->nd_procnum]) {
		NFSMCLGET(mreq, M_WAIT);
		nd->nd_mreq = mreq;
		nd->nd_mb = mreq;
	} else {
		NFSMGET(mreq);
		nd->nd_mreq = mreq;
		nd->nd_mb = mreq;
	}
	nd->nd_bpos = NFSMTOD(mreq, caddr_t);
	mbuf_setlen(mreq, 0);

	if ((nd->nd_flag & ND_GSSINITREPLY) == 0)
		NFSM_BUILD(nd->nd_errp, int *, NFSX_UNSIGNED);
}

/*
 * Parse an RPC request
 * - verify it
 * - fill in the cred struct.
 */
APPLESTATIC int
nfsrvd_getreq(struct nfsrv_descript *nd, NFSPROC_T *p, int iscb)
{
	int len, i;
	u_int32_t *tl;
	u_int32_t nfsvers, auth_type, prognum;
	int error = 0;
	struct nfsgss_handle *gssp;
	u_char *hp;
	u_int32_t seqnum;
	int gssproc, service, handlelen, datalen;

	/*
	 * Save the start position of the RPC header, for use by
	 * nfsgss_ckheader() later.
	 */
	nd->nd_startm = nd->nd_md;
	nd->nd_startpos = nd->nd_dpos;
	NFSM_DISSECT(tl, u_int32_t *, 8 * NFSX_UNSIGNED);
	nd->nd_retxid = fxdr_unsigned(u_int32_t, *tl++);
	if (*tl != newrpc_call)
		return (EBADRPC);
	tl++;
	nd->nd_repstat = 0;
	nd->nd_flag = 0;
	if (*tl++ != newrpc_vers) {
		nd->nd_repstat = NFSERR_RPCMISMATCH;
		return (0);
	}
	prognum = fxdr_unsigned(u_int32_t, *tl++);
	if ((!iscb && prognum != NFS_PROG) ||
	    (iscb && prognum != NFS_CALLBCKPROG))
		nd->nd_repstat = NFSERR_PROGUNAVAIL;
	nfsvers = fxdr_unsigned(u_int32_t, *tl++);
	if (!nd->nd_repstat &&
	    ((!iscb && (nfsvers < NFS_VER2 || nfsvers > NFS_VER4 ||
	      (nfsvers == NFS_VER4 && nd->nd_nam2 && !nfstest_v4udp))) ||
	     (iscb && nfsvers != NFSV4_CBVERS)))
		nd->nd_repstat = NFSERR_PROGMISMATCH;
	if (iscb)
		nd->nd_flag = (ND_NFSCB | ND_NFSV4);
	else if (nfsvers == NFS_VER4)
		nd->nd_flag = ND_NFSV4;
	else if (nfsvers == NFS_VER3)
		nd->nd_flag = ND_NFSV3;
	else
		nd->nd_flag = ND_NFSV2;
	nd->nd_procnum = fxdr_unsigned(u_int32_t, *tl++);
	if (nd->nd_procnum != NFSPROC_NULL &&
	    ((iscb && nd->nd_procnum != NFSV4PROC_CBCOMPOUND) ||
	     ((nd->nd_flag & ND_NFSV4) && nd->nd_procnum != NFSV4PROC_COMPOUND)
	     || nd->nd_procnum >= NFS_V3NPROCS ||
	     ((nd->nd_flag & ND_NFSV2) && nd->nd_procnum > NFSV2PROC_STATFS))) {
		if (!nd->nd_repstat)
		    nd->nd_repstat = NFSERR_PROCUNAVAIL;
	} else if (nd->nd_flag & ND_NFSV2)
		nd->nd_procnum = nfsv3_procid[nd->nd_procnum];
	auth_type = fxdr_unsigned(u_int32_t, *tl++);
	len = fxdr_unsigned(int, *tl);
	if (len < 0 || len > RPCAUTH_MAXSIZ)
		return (EBADRPC);
	if (nd->nd_repstat)
		return (0);

	/*
	 * For V4, filter IP#s here for the V4root.
	 */
	if (!iscb && (nd->nd_flag & ND_NFSV4)) {
		nd->nd_repstat = nfsvno_v4rootexport(nd);
		if (nd->nd_repstat)
			return (0);
	}
DEBUG2PRINTF("getreq proc=%d ndflag=0x%x autht=%d\n",nd->nd_procnum,nd->nd_flag,auth_type);

	/*
	 * Handle the various authentication flavours.
	 */
	switch (auth_type) {
	case RPCAUTH_NULL:
		/*
		 * RFC2623 suggests that the NFSv3 Fsinfo RPC be
		 * allowed over AUTH_NONE, so ND_AUTHNONE is set
		 * instead of an error.
		 */
		if (nd->nd_procnum != NFSPROC_NULL &&
		    nd->nd_procnum != NFSPROC_FSINFO)
			nd->nd_repstat = (NFSERR_AUTHERR | AUTH_REJECTCRED);
		else
			nd->nd_flag |= ND_AUTHNONE;
		break;
	case RPCAUTH_UNIX:
		if (len < RPCAUTHUNIX_MINSIZ)
			return (EBADRPC);
		NFSM_DISSECT(tl, u_int32_t *, 2 * NFSX_UNSIGNED);
		len = fxdr_unsigned(int, *++tl);
		if (len < 0 || len > NFS_MAXNAMLEN)
			return (EBADRPC);
		if (len > 0) {
			error = nfsm_advance(nd, NFSM_RNDUP(len), -1);
			if (error)
				return (error);
		}
		NFSM_DISSECT(tl, u_int32_t *, 3 * NFSX_UNSIGNED);
		nd->nd_cred->cr_uid = fxdr_unsigned(uid_t, *tl++);
		nd->nd_cred->cr_gid = fxdr_unsigned(gid_t, *tl++);
#ifdef MAC
		if (!iscb)
			mac_associate_nfsd_label(nd->nd_cred);
#endif

		/*
		 * Some BSDen make cr_gid the same location as
		 * cr_groups[0], others don't.
		 */
		nd->nd_cred->cr_groups[0] = nd->nd_cred->cr_gid;
		len = fxdr_unsigned(int, *tl);
		if (len < 0 || len > RPCAUTH_UNIXGIDS)
			return (EBADRPC);
		NFSM_DISSECT(tl, u_int32_t *, (len + 2) * NFSX_UNSIGNED);
		for (i = 1; i <= len; i++)
		    if (i < NGROUPS)
			nd->nd_cred->cr_groups[i] = fxdr_unsigned(gid_t,*tl++);
		    else
			tl++;
		nd->nd_cred->cr_ngroups = (len>=NGROUPS) ? NGROUPS : (len + 1);
		len = fxdr_unsigned(int, *++tl);
		if (len < 0 || len > RPCAUTH_MAXSIZ)
			return (EBADRPC);
		if (len > 0) {
			error = nfsm_advance(nd, NFSM_RNDUP(len), -1);
			if (error)
				return (error);
		}
		break;
	case RPCAUTH_GSS:
DEBUG2PRINTF("gss auth len=%d\n",len);
		if (len < GSSX_AUTHHEAD) {
			nd->nd_repstat = (NFSERR_AUTHERR | AUTH_BADCRED);
			return (0);
		}
		NFSM_DISSECT(tl, u_int32_t *, GSSX_AUTHHEAD);
		i = fxdr_unsigned(int, *tl++);
		gssproc = fxdr_unsigned(int, *tl++);
		nd->nd_seqnum = fxdr_unsigned(u_int32_t, *tl++);
		service = fxdr_unsigned(int, *tl++);
		handlelen = fxdr_unsigned(int, *tl);
		if (i != RPCAUTHGSS_VERS1) {
			/*
			 * In RFC2203, section 5.1 defines AUTH_REJECTCRED
			 * as the response for an invalid version #, however
			 * section 5.3.3.3 defines AUTH_BADCRED as the response
			 * for a version # different than used for context
			 * initialization. Since only vers == 1 is supported,
			 * which is the correct response?
			 */
			if (gssproc == RPCAUTHGSS_DATA ||
			    gssproc == RPCAUTHGSS_DESTROY)
			    nd->nd_repstat = (NFSERR_AUTHERR|AUTH_BADCRED);
			else
			    nd->nd_repstat = (NFSERR_AUTHERR|AUTH_REJECTCRED);
			return (0);
		}
DEBUG2PRINTF("proc=%d gssproc=%d seqnum=%d service=%d hlen=%d\n",nd->nd_procnum,gssproc,nd->nd_seqnum,service,handlelen);
		if (((gssproc == RPCAUTHGSS_INIT ||
		      gssproc == RPCAUTHGSS_CONTINIT ||
		      gssproc == RPCAUTHGSS_DESTROY) &&
		     nd->nd_procnum != NFSPROC_NULL) ||
		    (gssproc != RPCAUTHGSS_INIT &&
		     gssproc != RPCAUTHGSS_CONTINIT &&
		     gssproc != RPCAUTHGSS_DATA &&
		     gssproc != RPCAUTHGSS_DESTROY)) {
			nd->nd_repstat = (NFSERR_AUTHERR | AUTH_BADCRED);
			return (0);
		}
		if (gssproc == RPCAUTHGSS_INIT) {
			if (handlelen != 0) {
			    nd->nd_repstat = (NFSERR_AUTHERR | AUTH_BADCRED);
			    return (0);
			}
			/*
			 * Setting gssp to NULL tells nfsgss_continit()
			 * to make a new handle.
			 */
			gssp = NULL;
		} else {
			/*
			 * For some reason, some clients to an
			 * RCPAUTHGSS_DESTROY with a handlelen == 0 and
			 * expect AUTH_PROBCRED to be returned. Although
			 * I'd argue that a handlelen == 0 is bogus, I
			 * suppose AUTH_PROBCRED is what is suggested by
			 * Sec. 5.3.3.3 of RFC2203.
			 */
			if (handlelen != GSSX_MYHANDLE) {
			    if (gssproc == RPCAUTHGSS_CONTINIT)
				nd->nd_repstat = (NFSERR_AUTHERR |
				    AUTH_BADCRED);
			    else
				nd->nd_repstat = (NFSERR_AUTHERR |
				    AUTH_PROBCRED);
			    return (0);
			}
			NFSM_DISSECT(hp, u_char *, handlelen);
			i = nfsgss_gethandle(hp, &gssp,
			    (gssproc == RPCAUTHGSS_CONTINIT), p);
			if (i) {
				nd->nd_repstat = (NFSERR_AUTHERR | i);
				return (0);
			}
		}
		nd->nd_gssp = gssp;
		NFSM_DISSECT(tl, u_int32_t *, 2 * NFSX_UNSIGNED);
		if (gssproc == RPCAUTHGSS_INIT ||
		    gssproc == RPCAUTHGSS_CONTINIT) {
			/*
			 * Check to see the verifier is null.
			 */
			i = fxdr_unsigned(int, *tl++);
DEBUG2PRINTF("init verf=%d\n",i);
			if (i != RPCAUTH_NULL || *tl)
			    nd->nd_repstat = (NFSERR_AUTHERR | AUTH_BADCRED);

			/*
			 * Return for any nd_repstat, including ones before
			 * authentication, such as NFSERR_PROGUNAVAIL.
			 */
			if (nd->nd_repstat)
			    return (0);

			/*
			 * Pass the token up to userland and let the gssd
			 * do Context Initialization.
			 * nfsgss_continit() also creates the reply.
			 */
DEBUG2PRINTF("at continit\n");
			i = nfsgss_continit(nd, gssproc, &gssp, p);
			nd->nd_gssp = gssp;
			if (i) {
			    nd->nd_repstat = (NFSERR_AUTHERR | i);
			    return (0);
			}
DEBUG2PRINTF("aft continit\n");
		} else {
			i = fxdr_unsigned(int, *tl++);
DEBUG2PRINTF("data/destroy verf=%d\n",i);
			if (i != RPCAUTH_GSS) {
			    nd->nd_repstat = (NFSERR_AUTHERR|AUTH_REJECTCRED);
			    return (0);
			}
			if (service != RPCAUTHGSS_SVCNONE &&
			    service != RPCAUTHGSS_SVCINTEGRITY &&
			    service != RPCAUTHGSS_SVCPRIVACY) {
			    nd->nd_repstat = (NFSERR_AUTHERR | AUTH_BADCRED);
			    return (0);
			}

			/*
			 * Call nfsgss_ckheader() to checksum the RPC
			 * header.
			 */
			i = fxdr_unsigned(int, *tl);
DEBUG2PRINTF("at ckheader len=%d\n",i);
			i = nfsgss_ckheader(nd, i, GSSX_RPCHEADER, 0,
			    &gssp->nfsg_hand, gssp->nfsg_sched);
DEBUG2PRINTF("aft ckheader ret=%d\n",i);
			if (i) {
			    nd->nd_repstat = (NFSERR_AUTHERR | i);
			    return (0);
			}
			/*
			 * and the seqnum
			 */
			i = nfsgss_ckseqnum(nd, gssp);
DEBUG2PRINTF("aft ckseqnum ret=%d\n",i);
			if (i) {
			    nd->nd_repstat = NFSERR_DONTREPLY;
			    return (0);
			}
		}

		/*
		 * If there was an error detected prior to authentication,
		 * we return now, since we have what we need to create the
		 * RPCAUTH_GSS verifier.
		 */
		if (nd->nd_repstat)
		    return (0);

		if (gssproc == RPCAUTHGSS_DATA) {
		    /*
		     * Copy the equivalent server ucred into nd_cred->
		     */
		    newnfs_copycred(&gssp->nfsg_cr, nd->nd_cred);
#ifdef MAC
		    if (!iscb)
			mac_associate_nfsd_label(nd->nd_cred);
#endif
		    /*
		     * Now, handle data integrity/privacy.
		     */
DEBUG2PRINTF("do integ/priv\n");
		    if (service == RPCAUTHGSS_SVCINTEGRITY) {
			NFSM_DISSECT(tl, u_int32_t *, NFSX_UNSIGNED);
			datalen = fxdr_unsigned(int, *tl);
			if (datalen < NFSX_UNSIGNED) {
			    error = EBADRPC;
			    goto nfsmout;
			}
			nd->nd_startm = nd->nd_md;
			nd->nd_startpos = nd->nd_dpos;
			error = nfsm_advance(nd, datalen, -1);
			if (error)
			    goto nfsmout;
			NFSM_DISSECT(tl, u_int32_t *, NFSX_UNSIGNED);
			i = fxdr_unsigned(int, *tl);
			i = nfsgss_ckheader(nd, i, datalen, 0, &gssp->nfsg_hand,
			    gssp->nfsg_sched);
			if (i) {
			    nd->nd_repstat = NFSERR_GARBAGE;
			    return (0);
			}
			nd->nd_md = nd->nd_startm;
			nd->nd_dpos = nd->nd_startpos;
			NFSM_DISSECT(tl, u_int32_t *, NFSX_UNSIGNED);
			seqnum = fxdr_unsigned(u_int32_t, *tl);
			if (nd->nd_seqnum != seqnum) {
			    nd->nd_repstat = NFSERR_GARBAGE;
			    return (0);
			}
			nd->nd_flag |= ND_GSSINTEGRITY;
		    } else if (service == RPCAUTHGSS_SVCPRIVACY) {
			NFSM_DISSECT(tl, u_int32_t *, NFSX_UNSIGNED);
			datalen = fxdr_unsigned(int, *tl);
DEBUG2PRINTF("priv datalen=%d\n",datalen);
			if (datalen < GSSX_MINWRAP) {
			    error = EBADRPC;
			    goto nfsmout;
			}
			i = nfsgss_unwrap(nd, datalen, 0, gssp->nfsg_key,
			    gssp->nfsg_sched);
DEBUG2PRINTF("aft unwrap ret=%d\n",i);
			if (i) {
			    nd->nd_repstat = NFSERR_GARBAGE;
			    return (0);
			}
			NFSM_DISSECT(tl, u_int32_t *, NFSX_UNSIGNED);
			seqnum = fxdr_unsigned(u_int32_t, *tl);
DEBUG2PRINTF("priv seq=%d\n",seqnum);
			if (nd->nd_seqnum != seqnum) {
			    nd->nd_repstat = NFSERR_GARBAGE;
			    return (0);
			}
			nd->nd_flag |= ND_GSSPRIVACY;
		    }
		}
		if (gssproc == RPCAUTHGSS_DESTROY)
			nfsgss_destroy(gssp);
		break;
	default:
		nd->nd_repstat = (NFSERR_AUTHERR | AUTH_REJECTCRED);
	};
DEBUG2PRINTF("eo getreq mreq=%p stat=0x%x\n",(void *)nd->nd_mreq,nd->nd_repstat);
	return (0);
nfsmout:
	if (nd->nd_gssp)
		nfsgss_releasehandle(nd->nd_gssp, p);
	return (error);
}

#ifndef APPLE
/*
 * Derefence a server socket structure. If it has no more references and
 * is no longer valid, you can throw it away.
 */
void
nfsrvd_slpderef(struct nfssvc_sock *slp, struct newnfs_sockhead *shp)
{

	if (slp == NULL)
		panic("nfslpderef");
	if (--(slp->ns_sref) == 0 && (slp->ns_flag & SLP_VALID) == 0) {
		TAILQ_REMOVE(shp, slp, ns_chain);
		free((caddr_t)slp, M_NFSSVC);
	}
}
#endif	/* !APPLE */

/*
 * Build a reply header.
 */
APPLESTATIC void
nfsrv_replyheader(struct nfsrv_descript *nd, __unused struct nfssvc_sock *slp,
    NFSPROC_T *p)
{
	u_int32_t *tl;
	mbuf_t m;
	int i, j;
	u_char *cp;
	u_char calcsum[8];
	int datalen = 0;

	if (nd->nd_repstat == NFSERR_GARBAGE && nd->nd_mreq) {
		mbuf_freem(nd->nd_mreq);
		nd->nd_mreq = NULL;
	}

	/*
	 * Since this is going to use NFSM_BUILD() and also calls functions
	 * that do the same, some fiddling is done with nd_mb and nd_bpos.
	 * At times they point to the end of nd_mreq chain, which is the
	 * end of the NFS procedure reply args. At other times, they are
	 * used to build the reply header and reference the current
	 * build position in the header, pointed to by "m" later.
	 */
	if (nd->nd_mreq && (nd->nd_flag & (ND_GSSINTEGRITY | ND_GSSPRIVACY))) {
		/*
		 * First, prepend the sequence# and confounder, as required.
		 */
		if (nd->nd_flag & ND_GSSINTEGRITY) {
			M_PREPEND(nd->nd_mreq, NFSX_UNSIGNED, M_WAIT);
			tl = NFSMTOD(nd->nd_mreq, u_int32_t *);
			*tl = txdr_unsigned(nd->nd_seqnum);
		} else {
			M_PREPEND(nd->nd_mreq, 3 * NFSX_UNSIGNED, M_WAIT);
			tl = NFSMTOD(nd->nd_mreq, u_int32_t *);
			*tl++ = arc4random();
			*tl++ = arc4random();
			*tl = txdr_unsigned(nd->nd_seqnum);
DEBUG2PRINTF("prepended rand + seq=%d\n",nd->nd_seqnum);
		}

		/*
		 * Now, calculate the length and find the last mbuf.
		 */
		m = nd->nd_mreq;
		i = mbuf_len(m);
		while (mbuf_next(m)) {
			m = mbuf_next(m);
			i += mbuf_len(m);
		}
		datalen = i;
DEBUG2PRINTF("replyheader datalen=%d\n",datalen);

		/*
		 * If this reply came out of the recent request cache,
		 * nd_mb and nd_bpos need to be set.
		 */
		nd->nd_mb = m;
		nd->nd_bpos = NFSMTOD(m, caddr_t) + mbuf_len(m);
		/*
		 * For RPCAUTHGSS_INTEGRITY or RPCAUTHGSS_PRIVACY, finish
		 * off the reply data.
		 */
		if (nd->nd_flag & ND_GSSINTEGRITY) {
			nd->nd_startm = nd->nd_mreq;
			nd->nd_startpos = NFSMTOD(nd->nd_mreq, caddr_t);
			nfsgss_desmacmd5(nd, datalen, nd->nd_gssp->nfsg_sched,
				RPCAUTHGSS_MIC, calcsum);
			(void)nfsgss_createtok(nd, nd->nd_gssp->nfsg_sched, 0,
				calcsum, 0, RPCAUTHGSS_MIC);
		} else {
			/*
			 * For Privacy, append the pad byte(s).
			 */
			i = 8 - (datalen % 8);
			NFSM_BUILD(cp, u_char *, i);
			for (j = 0; j < i; j++)
				*cp++ = (u_char)i;
			datalen += i;
DEBUG2PRINTF("aft pad=%d datalen=%d\n",i,datalen);
			i = nfsgss_lengthtok(nd, datalen);
			i = NFSM_RNDUP(i) - i;
			if (i > 0) {
DEBUG2PRINTF("priv xdrpad i=%d\n",i);
				NFSM_BUILD(cp, u_char *, i);
				for (j = 0; j < i; j++)
					*cp++ = 0x0;
			}

			nd->nd_startm = nd->nd_mreq;
			nd->nd_startpos = NFSMTOD(nd->nd_mreq, caddr_t);
			/*
			 * Calculate the checksum on the padded plain text
			 * data.
			 */
			nfsgss_wrapcksum(nd, datalen, calcsum);

			/*
			 * Then encrypt it.
			 */
			i = nfsgss_des(nd->nd_startm, nd->nd_startpos, datalen,
				nd->nd_gssp->nfsg_key, 1);
			if (i)
				panic("nfsgss des");
		}
	}
	/*
	 * From here on, "m" is the head of the reply header list and
	 * nd_md, nd_bpos reference its current build position. nd_mreq
	 * refers to the NFS procedure reply args, until just before
	 * returning, when it is set to "m".
	 */
	NFSMGETHDR(m);
	/*
	 * try and leave leading space for the lower level headers.
	 */
	if (nd->nd_flag & ND_STREAMSOCK) {
		NFSM_DATAP(m, max_hdr + NFSX_UNSIGNED);
	} else {
		NFSM_DATAP(m, max_hdr);
	}
	tl = NFSMTOD(m, u_int32_t *);
	mbuf_setlen(m, 4 * NFSX_UNSIGNED);
	nd->nd_mb = m;
	nd->nd_bpos = ((caddr_t)tl) + mbuf_len(m);
	*tl++ = txdr_unsigned(nd->nd_retxid);
	*tl++ = newrpc_reply;
	if (nd->nd_repstat == NFSERR_RPCMISMATCH ||
	    (nd->nd_repstat & NFSERR_AUTHERR)){
		*tl++ = newrpc_msgdenied;
		if (nd->nd_repstat & NFSERR_AUTHERR) {
			*tl = newrpc_autherr;
			NFSM_BUILD(tl, u_int32_t *, NFSX_UNSIGNED);
			*tl = txdr_unsigned(nd->nd_repstat & ~NFSERR_AUTHERR);
		} else {
			*tl = newrpc_mismatch;
			NFSM_BUILD(tl, u_int32_t *, 2 * NFSX_UNSIGNED);
			*tl++ = txdr_unsigned(RPC_VER2);
			*tl = txdr_unsigned(RPC_VER2);
		}
		if (nd->nd_gssp)
			nfsgss_releasehandle(nd->nd_gssp, p);
		nd->nd_mreq = m; /* and make the reply include the header */
		return;
	}
	*tl++ = newrpc_msgaccepted;
	if (nd->nd_flag & ND_GSS) {
		*tl = txdr_unsigned(RPCAUTH_GSS);
		i = nfsgss_verf(nd, nd->nd_gssp->nfsg_sched,
		    nd->nd_seqnum, NULL, 0, NULL);
		if (i) {
			/*
			 * Shouldn't ever happen, but turn it into a
			 * NULL verifier. For some reason the GSS
			 * mechanism was unknown.
			 */
			printf("nfsrv_replyheader failed gss verf\n");
			*tl = 0;
			NFSM_BUILD(tl, u_int32_t *, 2 * NFSX_UNSIGNED);
			*tl++ = 0;
		} else {
			NFSM_BUILD(tl, u_int32_t *, NFSX_UNSIGNED);
		}
	} else {
		/*
		 * RPCAUTH_NULL verifier.
		 */
		*tl = 0;
		NFSM_BUILD(tl, u_int32_t *, 2 * NFSX_UNSIGNED);
		*tl++ = 0;
	}

	if (nd->nd_mreq) {
		/*
		 * There is an NFS response. Fill in RPCGSS header stuff
		 * as required, and then attach the response to the chain.
		 */
		*tl = 0;	/* SUCCESS */
		if (nd->nd_flag & ND_GSSINTEGRITY) {
			/*
			 * For data integrity, all you need is the
			 * length field for the opaque data.
			 */
			NFSM_BUILD(tl, u_int32_t *, NFSX_UNSIGNED);
			*tl = txdr_unsigned(datalen);
		} else if (nd->nd_flag & ND_GSSPRIVACY) {
			/*
			 * For privacy, create the wrap token header.
			 */
			i = nfsgss_createtok(nd, nd->nd_gssp->nfsg_sched, 0,
				calcsum, datalen, RPCAUTHGSS_WRAP);
DEBUG2PRINTF("aft priv createtok datalen=%d len=%d\n",datalen,i);
		}
		mbuf_setnext(nd->nd_mb, nd->nd_mreq);
	} else {
		switch (nd->nd_repstat) {
		case NFSERR_PROGUNAVAIL:
			*tl = txdr_unsigned(RPC_PROGUNAVAIL);
			break;
		case NFSERR_PROGMISMATCH:
		case NFSERR_PROGNOTV4:
			*tl = txdr_unsigned(RPC_PROGMISMATCH);
			NFSM_BUILD(tl, u_int32_t *, 2 * NFSX_UNSIGNED);
			if (nd->nd_repstat == NFSERR_PROGMISMATCH)
				*tl++ = txdr_unsigned(NFS_VER2);
			else
				*tl++ = txdr_unsigned(NFS_VER4);
			*tl = txdr_unsigned(NFS_VER4);
			break;
		case NFSERR_PROCUNAVAIL:
			*tl = txdr_unsigned(RPC_PROCUNAVAIL);
			break;
		case NFSERR_RETVOID:
			*tl = 0;	/* SUCCESS */
			break;
		case NFSERR_GARBAGE:
		default:
			*tl = txdr_unsigned(RPC_GARBAGE);
			break;
		};
	}
	if (nd->nd_gssp)
		nfsgss_releasehandle(nd->nd_gssp, p);
	nd->nd_mreq = m;	/* and make the reply include the header */
}

